#include "rphoton.hpp"

using namespace TDP::Math;

void rpLightVolume::ContribAtPoint(const terVec3 &p, terVec3 &outColor, terVec3 &outDirection) const
{
	if(this->isPoint)
	{
		terVec3 diff = (p - this->v);
		terReal lengthSquared = diff.LengthSquared();

		outDirection = diff / sqrtf(lengthSquared);
		outColor = this->color / (lengthSquared * terPi_k * 4.0f);
	}
	else
	{
		outDirection = this->v;
		outColor = this->color;
	}
}


// TODO: Model exclusion

void InsertPolyLeafTree(rpLightVolumeTreeNode *vol)
{
}

struct rpClipPoint
{
	terVec3 point;
	enum edgeClipTypeEnum
	{
		notClipped_k = 0,
		clippedByDivider_k = 1,
		clippedByOccluder_k = 2,
	} edgeClipType;
};

bool ClipPointsToPlane(Plane plane, const rpClipPoint *points, unsigned int numPoints, rpClipPoint *outPoints, unsigned int *outNumPoints,
	Plane::Side includeSide, rpClipPoint::edgeClipTypeEnum edgeClipType)
{
	unsigned int numNewPoints = 0;
	bool anyIncluded = false;

	// Find the first point that isn't on the inclusion side
	for(unsigned int i=0;i<numPoints;i++)
	{
		const rpClipPoint *thisPoint = points + i;
		const rpClipPoint *nextPoint = points + ((i+1)%numPoints);
		Plane::Side thisSide = plane.PointSide(thisPoint->point);
		Plane::Side nextSide = plane.PointSide(nextPoint->point);

		if(thisSide == includeSide)
		{
			anyIncluded = true;
			outPoints[numNewPoints++] = *thisPoint;

			if(nextSide != includeSide && nextSide != Plane::On_k)
			{
				outPoints[numNewPoints].point = plane.IntersectWithLine(Line(thisPoint->point, nextPoint->point));
				outPoints[numNewPoints].edgeClipType = edgeClipType;	// The next point will either be on the plane, or an intersect placed on the plane
				numNewPoints++;
			}
		}
		else if(thisSide == Plane::On_k)
		{
			outPoints[numNewPoints] = *thisPoint;
			if(nextSide == Plane::On_k)
				outPoints[numNewPoints].edgeClipType = edgeClipType;
			numNewPoints++;
		}
		else	// On the backside
		{
			if(nextSide == includeSide)
			{
				outPoints[numNewPoints].point = plane.IntersectWithLine(Line(thisPoint->point, nextPoint->point));
				outPoints[numNewPoints].edgeClipType = thisPoint->edgeClipType;
				numNewPoints++;
			}
		}
	}

	*outNumPoints = numNewPoints;
	return anyIncluded;
}

rpLightVolumeTreeNode *CloneLeaf(const rpLightVolumeTreeNode *node)
{
	terSafePointer<rpLightVolumeTreeNode> newNode = new rpLightVolumeTreeNode();
	newNode->flags = node->flags;
	newNode->splitPlane = node->splitPlane;
	newNode->front = NULL;
	newNode->back = NULL;

	return newNode.Unguard();
}


// Inserts a poly into a beam tree
// Returns true if the tree node winds up being a mergeable "clean occluder" node, meaning the inserted polygon does not intersect
// any existing occluders and splits the node into an open node and an occluded node

bool InsertPolyBeamTree(const rpLightVolume *vol, rpLightVolumeTreeNode *node, const rpClipPoint *points, unsigned int numPoints, const Plane &basePlane)
{
	if(node->flags & rpLightVolumeTreeNode::flagIsLeaf_k)
	{
		bool occludesEntireBackside = true;

		// Add edge clips
		for(unsigned int i=0;i<numPoints;i++)
		{
			const rpClipPoint *thisPoint = points + i;
			const rpClipPoint *nextPoint = points + ((i+1)%numPoints);
			Plane clipPlane;

			// All clips must be divider edges in order to count as a total occlusion of the node backside
			// If the polygon clipped off of an occluder or is generating its own dividers, then this isn't
			// an optimizable divider node
			if(thisPoint->edgeClipType != rpClipPoint::clippedByDivider_k)
				occludesEntireBackside = false;

			if(thisPoint->edgeClipType != rpClipPoint::notClipped_k)
				continue;	// This edge was already on a BSP split plane so it's not valid for generating a new split plane

			if(vol->isPoint)
				clipPlane = Triangle(thisPoint->point, nextPoint->point, vol->v).Plane();
			else
				clipPlane = Triangle(nextPoint->point - thisPoint->point, terVec3(0.0f), vol->v).Plane();

			// Make sure this was done right!
			for(unsigned int i=0;i<numPoints;i++)
			{
				terVec3 catchPoint = points[i].point;
				if(clipPlane.PointSide(points[i].point) == Plane::Front_k)
				{
					clipPlane.PointSide(points[i].point);
					throw "Screwed up!";
				}
			}

			// Split this node
			node->front = CloneLeaf(node);
			node->back = CloneLeaf(node);
			node->splitPlane = clipPlane;
			if(!node->flags)
				throw "Fuck";
			node->flags ^= (rpLightVolumeTreeNode::flagIsLeaf_k);

			node = node->back;
		}

		// Add the primary clip
		node->front = CloneLeaf(node);
		node->splitPlane = basePlane;
		if(!node->flags)
			throw "Fuck";
		node->flags ^= (rpLightVolumeTreeNode::flagIsLeaf_k);

		// TODO: Translucent creates a back node

		return occludesEntireBackside;
	}

	// Not a leaf node
	unsigned int maxNewPoints = numPoints + 1;
	rpClipPoint staticClipPoints[8];
	rpClipPoint *newClipPoints = staticClipPoints;
	terVector<rpClipPoint> dynamicClipPoints;
	unsigned int numNewClipPoints;

	if(maxNewPoints > 8)
	{
		dynamicClipPoints.Alloc(maxNewPoints);
		newClipPoints = dynamicClipPoints;
	}

	// A clean occluder optimization can occur as follows:
	// - The front side is a clean occluder
	// - The back side is either occluded, or is also a clean occluder

	bool backOccluded = false;
	bool frontOccluded = false;

	rpClipPoint::edgeClipTypeEnum clipType;
	if(node->back == NULL)
	{
		clipType = rpClipPoint::clippedByOccluder_k;
		backOccluded = true;
	}
	else
	{
		clipType = rpClipPoint::clippedByDivider_k;
		if(ClipPointsToPlane(node->splitPlane, points, numPoints, newClipPoints, &numNewClipPoints, Plane::Back_k, clipType))
			backOccluded = InsertPolyBeamTree(vol, node->back, newClipPoints, numNewClipPoints, basePlane);
	}

	if(ClipPointsToPlane(node->splitPlane, points, numPoints, newClipPoints, &numNewClipPoints, Plane::Front_k, clipType))
		frontOccluded = InsertPolyBeamTree(vol, node->front, newClipPoints, numNewClipPoints, basePlane);

	if(frontOccluded && backOccluded)
	{
		// The front was a clean occlude, the back was either a clean occlude or doesn't exist

		terSafePointer<rpLightVolumeTreeNode> oldChildFront;
		oldChildFront = node->front->front.Unguard();

		node->front.Discard();
		if(node->back != NULL)
			node->back.Discard();

		node->front = oldChildFront.Unguard();
		node->back = NULL;

		return true;
	}
	return false;
}

void InsertBeamTreeFace(rpLightVolume *vol, unsigned int faceNum)
{
	for(unsigned int i=0;i<manifolds.Count();i++)
	{
		const rpManifold *manif = manifolds + i;
		if(manif->seg->originalFaceIndex != faceNum)
			continue;

		unsigned int numTris = manif->seg->numTris;
		const unsigned int *tris = manif->triData;
		const terBRBSPCommonVert *verts = manif->vertData;

		for(unsigned int ti=0;ti<numTris;ti++,tris+=3)
		{
			rpClipPoint points[3];
			for(int i=0;i<3;i++)
			{
				points[i].point = verts[tris[i]].position;
				points[i].edgeClipType = rpClipPoint::notClipped_k;
			}

			Triangle tri(points[0].point, points[1].point, points[2].point);

			if(vol->isPoint)
			{
				if(tri.Plane().PointSide(vol->v) == Plane::Back_k)
					continue;	// Behind the plane
			}
			else
			{
				if(tri.Plane().Normal().DotProduct(vol->v) > 0)
					continue;	// Facing the wrong way
			}

			InsertPolyBeamTree(vol, vol->rootNode, points, 3, tri.Plane());
		}
	}
}

void InsertBeamTreeLeaf(rpLightVolume *vol, unsigned char *faceTested, int leafNum)
{
	const terBRBSPLeaf *leaf = geometry._leafs + leafNum;
	const unsigned int *faces = leaf->leafFaces;

	for(unsigned int i=0;i<leaf->numLeafFaces;i++)
	{
		unsigned int faceNum = faces[i];
		unsigned int byteNum = faceNum/8;
		unsigned char bit = (unsigned char)(1<<(faceNum%8));

		if((faceTested[byteNum] & bit) == 0)
		{
			faceTested[byteNum] |= bit;

			InsertBeamTreeFace(vol, faceNum);
		}
	}
}

void RecursiveInsertBeamTree(rpLightVolume *vol, unsigned char *faceTested, int nodeIndex)
{
	const terBRBSPNode *node = geometry._nodes + nodeIndex;

	bool frontFirst;
	if(vol->isPoint)
		frontFirst = (node->planeNormal.DotProduct(vol->v) >= node->planeDist);
	else
		frontFirst = (node->planeNormal.DotProduct(vol->v) < 0.0f);

	int nodeOrder[2] = { 0, 1 };
	if(!frontFirst)
	{
		nodeOrder[0] = 1;
		nodeOrder[1] = 0;
	}

	for(int i=0;i<2;i++)
	{
		int childIndex = node->children[nodeOrder[i]];

		if(childIndex < 0)
			InsertBeamTreeLeaf(vol, faceTested, -(childIndex+1));
		else
			RecursiveInsertBeamTree(vol, faceTested, childIndex);
	}
}

void CastBeamtree(rpLightVolume *vol)
{
	vol->rootNode = new rpLightVolumeTreeNode();
	vol->rootNode->flags = rpLightVolumeTreeNode::flagIsLeaf_k;

	terVector<unsigned char> faceTested;
	faceTested.Alloc((geometry._numFaces+7)/8, 0);

	RecursiveInsertBeamTree(vol, faceTested, 0);
}


// Photon mapping job manager
class rpBeamtreeWorkUnit : public Threading::WorkUnit
{
	terSafePointer<terMutex> _mtx;
	class Threading::Job *_parent;
	rpLightVolume *_vol;

public:
	void SetVolume(rpLightVolume *vol)
	{
		_vol = vol;
	}

	rpBeamtreeWorkUnit()
	{
		_mtx = ThreadManager->CreateMutex();
	}

	void Work()
	{
		CastBeamtree(_vol);

		_mtx->Release();
	}

	bool TryAcquire()
	{
		return _mtx->TryAcquire();
	}
};


class rpBeamtreeJob : public Threading::DecomposableJob
{
	rpLightVolume *_vol;

public:
	rpBeamtreeJob(Threading::JobManager *parent, rpLightVolume *vol)
		: DecomposableJob(parent, 1)
	{
		_vol = vol;
	}

	~rpBeamtreeJob()
	{
	}

	void PrepareWorkUnit(Threading::WorkUnit *wu)
	{
		rpBeamtreeWorkUnit *btu = (rpBeamtreeWorkUnit *)wu;
		terVec3 permuteOffset;

		btu->SetVolume(_vol);
	}

	Threading::WorkUnit *NewWorkUnit()
	{
		return NULL;
	}
};

void CastLightVolumes(terFileSystem *fs, const terLinkedList<rpObject> *objects, Threading::JobManager *jm)
{
	// Create point light shooters
	for(const terListLink<rpObject> *objLL = objects->First();objLL;objLL=objLL->Next())
	{
		const rpObject *obj = objLL->DataPtr();

		const rpObjectProperty *classname = obj->GetProperty("classname");
		const rpObjectProperty *intensity = obj->GetProperty("light");
		if(!intensity)
			intensity = obj->GetProperty("_light");
		const rpObjectProperty *color = obj->GetProperty("_color");
		const rpObjectProperty *origin = obj->GetProperty("origin");

		if(!classname || !origin)
			continue;

		float intensityV = config.defaultPointIntensity;
		if(intensity)
			intensityV = intensity->value.ToFloat();

		terVec3 colorV = terVec3(1.0f, 1.0f, 1.0f);
		if(color)
			colorV = ParseVector(color->value);

		float softV = config.defaultLightEmissionRadius;

		if(classname->value == "light")
		{
			rpLightVolume vol;

			vol.color = colorV * intensityV * config.photonsPointScale;
			vol.isPoint = true;
			vol.v = ParseVector(origin->value);
			vol.rootNode = new rpLightVolumeTreeNode();
			vol.rootNode->flags = rpLightVolumeTreeNode::flagIsLeaf_k;

			// Add a job for this
			terSafePointer<Threading::Job> emitter =
				new rpBeamtreeJob(jm, &vol);
			jm->AddJob(emitter);
		}
	}

	while(jm->ActiveJobs())
	{
		ThreadManager->SleepThread(1000);
	}
}
